/**
 * 通用请求函数
 * 支持微信小程序环境，包含错误处理和重试机制
 */

import { API_BASE_PATH, REQUEST_TIMEOUT, MAX_RETRY_COUNT, RETRY_DELAY, OPENID_CACHE_DURATION } from './config'

/**
 * 请求配置接口
 */
export interface RequestOptions {
  /** 请求路径 */
  url: string
  /** 请求方法，默认 POST */
  method?: 'GET' | 'POST' | 'PUT' | 'DELETE'
  /** 请求数据 */
  data?: any
  /** 请求头 */
  header?: Record<string, string>
  /** 是否显示加载提示 */
  showLoading?: boolean
  /** 加载提示文本 */
  loadingText?: string
  /** 是否显示错误提示 */
  showError?: boolean
  /** 自定义错误处理 */
  onError?: (error: any) => void
  /** 是否禁用重试 */
  disableRetry?: boolean
}

/**
 * 响应数据接口
 */
export interface ApiResponse<T = any> {
  code?: number
  message?: string
  data?: T
  [key: string]: any
}

/**
 * OpenID 缓存键名
 */
const OPENID_CACHE_KEY = 'x-wxuser'
const OPENID_EXPIRY_KEY = 'x-wxuser-expiry'

/**
 * 保存 OpenID 到本地存储
 * 如果 OPENID_CACHE_DURATION 为 null，则长期有效（不设置过期时间）
 * 如果 OPENID_CACHE_DURATION 为数字，则设置对应的过期时间
 * @param openid 用户的 OpenID
 */
export function saveOpenID(openid: string): void {
  try {
    // #ifdef MP-WEIXIN
    uni.setStorageSync(OPENID_CACHE_KEY, openid)
    
    if (OPENID_CACHE_DURATION === null) {
      // 长期有效，不设置过期时间
      uni.removeStorageSync(OPENID_EXPIRY_KEY) // 清除可能存在的过期时间
      console.log('OpenID 已保存，长期有效')
    } else {
      // 设置过期时间
      const expiryTime = Date.now() + OPENID_CACHE_DURATION
      uni.setStorageSync(OPENID_EXPIRY_KEY, expiryTime.toString())
      console.log('OpenID 已保存，过期时间:', new Date(expiryTime).toLocaleString())
    }
    // #endif
  } catch (error) {
    console.error('保存 OpenID 失败:', error)
  }
}

/**
 * 获取用户认证token（OpenID）
 * 从本地存储中获取 x-wxuser 值，并检查是否过期
 * 如果配置为长期有效（OPENID_CACHE_DURATION 为 null），则不检查过期时间
 * @returns OpenID 字符串，如果不存在或已过期则返回 null
 */
function getAuthToken(): string | null {
  try {
    // #ifdef MP-WEIXIN
    const openid = uni.getStorageSync(OPENID_CACHE_KEY)
    
    // 如果没有 openid，返回 null
    if (!openid) {
      return null
    }
    
    // 如果配置为长期有效，直接返回 openid
    if (OPENID_CACHE_DURATION === null) {
      return openid
    }
    
    // 如果配置了过期时间，检查是否过期
    const expiryTimeStr = uni.getStorageSync(OPENID_EXPIRY_KEY)
    
    // 如果没有过期时间，说明可能是旧数据（从长期有效切换到有时效），清除并返回 null
    if (!expiryTimeStr) {
      console.warn('OpenID 缺少过期时间，清除缓存')
      clearOpenID()
      return null
    }
    
    // 检查是否过期
    const expiryTime = parseInt(expiryTimeStr, 10)
    const now = Date.now()
    
    if (now >= expiryTime) {
      // 已过期，清除缓存
      console.warn('OpenID 已过期，清除缓存')
      clearOpenID()
      return null
    }
    
    return openid
    // #endif
    // #ifndef MP-WEIXIN
    return null
    // #endif
  } catch (error) {
    console.error('获取认证token失败:', error)
    return null
  }
}

/**
 * 检查 OpenID 是否有效（未过期）
 * @returns 如果 OpenID 存在且未过期返回 true，否则返回 false
 */
export function hasValidOpenID(): boolean {
  return getAuthToken() !== null
}

/**
 * 清除 OpenID 缓存
 */
export function clearOpenID(): void {
  try {
    // #ifdef MP-WEIXIN
    uni.removeStorageSync(OPENID_CACHE_KEY)
    uni.removeStorageSync(OPENID_EXPIRY_KEY)
    console.log('OpenID 缓存已清除')
    // #endif
  } catch (error) {
    console.error('清除 OpenID 缓存失败:', error)
  }
}

/**
 * 延迟函数
 */
function delay(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms))
}

/**
 * 显示加载提示
 */
function showLoading(text?: string) {
  uni.showLoading({
    title: text || '加载中...',
    mask: true
  })
}

/**
 * 隐藏加载提示
 */
function hideLoading() {
  uni.hideLoading()
}

/**
 * 显示错误提示
 */
function showError(message: string) {
  uni.showToast({
    title: message || '请求失败，请重试',
    icon: 'none',
    duration: 2000
  })
}

/**
 * 通用请求函数
 * @param options 请求配置
 * @param retryCount 当前重试次数
 * @returns Promise<T>
 */
/**
 * 检查是否是授权错误（不重试）
 * 检查 {code: 1002, message: "ERROR_CODE_UNAUTHORIZED"} 格式
 */
function isUnauthorizedError(error: any): boolean {
  if (error && typeof error === 'object') {
    return error.code === 1002 && error.message === 'ERROR_CODE_UNAUTHORIZED'
  }
  return false
}

/**
 * 检查是否是 code2session 接口（不重试）
 */
function isCode2SessionRequest(url: string): boolean {
  return url.includes('/code2session') || url.endsWith('code2session')
}

export async function request<T = any>(
  options: RequestOptions,
  retryCount: number = 0
): Promise<T> {
  const {
    url,
    method = 'POST',
    data,
    header = {},
    showLoading: needLoading = true,
    loadingText,
    showError: needError = true,
    onError,
    disableRetry = false
  } = options

  // 显示加载提示
  if (needLoading && retryCount === 0) {
    showLoading(loadingText)
  }
  
  // 构建完整URL
  const fullUrl = url.startsWith('http') ? url : `${API_BASE_PATH}${url}`
  
  // 构建请求头
  const requestHeader: Record<string, string> = {
    'Content-Type': 'application/json',
    ...header
  }

  // 添加认证头（除了 code2session 接口之外）
  const isCode2Session = isCode2SessionRequest(url)
  if (!isCode2Session) {
    const authToken = getAuthToken()
  if (authToken) {
      requestHeader['X-WXUSER'] = authToken
    }
  }

  try {
    // 发起请求
    const response = await new Promise<UniApp.RequestSuccessCallbackResult>((resolve, reject) => {
      uni.request({
        url: fullUrl,
        method,
        data,
        header: requestHeader,
        timeout: REQUEST_TIMEOUT,
        success: (res) => {
          resolve(res)
        },
        fail: (err) => {
          reject(err)
        }
      })
    })

    // 隐藏加载提示
    if (needLoading && retryCount === 0) {
      hideLoading()
    }

    // 检查HTTP状态码
    if (response.statusCode >= 200 && response.statusCode < 300) {
      const responseData = response.data as ApiResponse<T>
      
      // 检查业务状态码（根据实际后端返回格式调整）
      if (responseData.code !== undefined && responseData.code !== 0 && responseData.code !== 200) {
        const errorMessage = responseData.message || '请求失败'
        
        // 检查是否是授权错误（不重试）
        if (isUnauthorizedError(responseData)) {
          console.warn('授权错误，不重试:', responseData)
          if (needError) {
            showError(errorMessage)
          }
          if (onError) {
            onError(responseData)
          }
          // 抛出包含原始错误信息的错误对象，以便在 catch 块中也能识别
          const authError: any = new Error(errorMessage)
          authError.code = responseData.code
          authError.message = responseData.message
          throw authError
        }
        
        // 如果是认证失败，可能需要重新登录
        if (responseData.code === 401 || responseData.code === 403) {
          // 可以在这里触发重新登录逻辑
          console.error('认证失败，需要重新登录')
        }
        
        if (needError) {
          showError(errorMessage)
        }
        
        if (onError) {
          onError(responseData)
        }
        
        // 抛出包含原始错误信息的错误对象，以便在 catch 块中也能识别错误码
        const businessError: any = new Error(errorMessage)
        businessError.code = responseData.code
        businessError.message = responseData.message
        businessError.details = responseData.details
        throw businessError
      }
      
      // 返回数据
      return (responseData.data !== undefined ? responseData.data : responseData) as T
    } else {
      // HTTP状态码错误
      const responseData = response.data as ApiResponse<T>
      
      // 检查响应数据中是否包含授权错误
      if (isUnauthorizedError(responseData)) {
        const errorMessage = responseData.message || '授权错误'
        console.warn('授权错误，不重试:', responseData)
        if (needError) {
          showError(errorMessage)
        }
        if (onError) {
          onError(responseData)
        }
        // 抛出包含原始错误信息的错误对象，以便在 catch 块中也能识别
        const authError: any = new Error(errorMessage)
        authError.code = responseData.code
        authError.message = responseData.message
        throw authError
      }
      
      const errorMessage = `请求失败 (${response.statusCode})`
      
      // 检查是否应该重试
      const isCode2Session = isCode2SessionRequest(url)
      
      // 判断是否需要重试（HTTP状态码错误时）
      if (!disableRetry && !isCode2Session && retryCount < MAX_RETRY_COUNT && response.statusCode >= 500) {
        console.warn(`请求失败，准备重试 (${retryCount + 1}/${MAX_RETRY_COUNT}):`, errorMessage)
        await delay(RETRY_DELAY * (retryCount + 1)) // 递增延迟
        return request(options, retryCount + 1)
      }
      
      if (needError) {
        showError(errorMessage)
      }
      
      if (onError) {
        onError({ code: response.statusCode, message: errorMessage })
      }
      
      // 抛出包含状态码的错误对象
      const httpError: any = new Error(errorMessage)
      httpError.statusCode = response.statusCode
      httpError.code = response.statusCode
      throw httpError
    }
  } catch (error: any) {
    // 隐藏加载提示
    if (needLoading && retryCount === 0) {
      hideLoading()
    }

    console.error('请求错误:', error)

    // 检查是否应该重试
    // 以下情况不重试：
    // 1. 禁用了重试选项
    // 2. code2session 接口
    // 3. 授权错误（code: 1002, message: ERROR_CODE_UNAUTHORIZED）
    const isCode2Session = isCode2SessionRequest(url)
    const isUnauthorized = isUnauthorizedError(error)
    
    if (disableRetry || isCode2Session || isUnauthorized) {
      console.warn('请求失败，不重试:', {
        disableRetry,
        isCode2Session,
        isUnauthorized,
        error: error.message || error.errMsg
      })
    } else {
    // 判断是否需要重试
    // 网络错误、超时等可重试的错误
    const shouldRetry = 
      retryCount < MAX_RETRY_COUNT &&
      (
        !error.statusCode || // 网络错误
        error.statusCode >= 500 || // 服务器错误
        error.errMsg?.includes('timeout') || // 超时
        error.errMsg?.includes('fail') // 请求失败
      )

    if (shouldRetry) {
      console.warn(`请求失败，准备重试 (${retryCount + 1}/${MAX_RETRY_COUNT}):`, error.message || error.errMsg)
      await delay(RETRY_DELAY * (retryCount + 1)) // 递增延迟
      return request(options, retryCount + 1)
      }
    }

    // 不再重试，返回错误
    const errorMessage = error.message || error.errMsg || '网络错误，请检查网络连接'
    
    if (needError) {
      showError(errorMessage)
    }
    
    if (onError) {
      onError(error)
    }
    
    throw error
  }
}

/**
 * POST 请求便捷方法
 */
export function post<T = any>(url: string, data?: any, options?: Partial<RequestOptions>): Promise<T> {
  return request<T>({
    url,
    method: 'POST',
    data,
    ...(options || {})
  })
}

/**
 * GET 请求便捷方法
 */
export function get<T = any>(url: string, data?: any, options?: Partial<RequestOptions>): Promise<T> {
  return request<T>({
    url,
    method: 'GET',
    data,
    ...(options || {})
  })
}

